package cn.selfiot.iotman.helper;

import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import javax.net.ssl.*;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;

/**
 * 安全证书操作组件
 */
public class SSLHelper {
    /**
     * Java密钥库(Java Key Store，JKS)JKS_KEYSTORE
     */
    private static final String JKS_KEYSTORE = "JKS";
    private static final String X509 = "X.509";
    private static final String SunX509 = "SunX509";
    private static final String SSL = "SSL";
    private static final String PKIX = "PKIX";
    
    /**
     * 私钥加密
     *
     * @param data
     * @param keyStorePath
     * @param alias
     * @param password
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
                                             String alias, String password) throws Exception {
        // 取得私钥
        PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
        
        // 对数据加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        
        return cipher.doFinal(data);
        
    }
    
    /**
     * 由KeyStore获得私钥
     *
     * @param keyStorePath
     * @param alias
     * @param password
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(String keyStorePath, String alias,
                                            String password) throws Exception {
        KeyStore ks = getKeyStore(keyStorePath, password);
        PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
        return key;
    }
    
    /**
     * 获得KeyStore
     *
     * @param keyStorePath
     * @param password
     * @return
     * @throws Exception
     */
    private static KeyStore getKeyStore(String keyStorePath, String password)
            throws Exception {
        FileInputStream is = new FileInputStream(keyStorePath);
        KeyStore ks = KeyStore.getInstance(JKS_KEYSTORE);
        ks.load(is, password.toCharArray());
        is.close();
        return ks;
    }
    
    /**
     * 私钥解密
     *
     * @param data
     * @param keyStorePath
     * @param alias
     * @param password
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
                                             String alias, String password) throws Exception {
        // 取得私钥
        PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
        
        // 对数据加密
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        
        return cipher.doFinal(data);
        
    }
    
    /**
     * 公钥加密
     *
     * @param data
     * @param certificatePath
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
            throws Exception {
        // 取得公钥
        PublicKey publicKey = getPublicKey(certificatePath);
        // 对数据加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        
        return cipher.doFinal(data);
    }
    
    /**
     * 由Certificate获得公钥
     *
     * @param certificatePath
     * @return
     * @throws Exception
     */
    private static PublicKey getPublicKey(String certificatePath)
            throws Exception {
        Certificate certificate = getCertificate(certificatePath);
        PublicKey key = certificate.getPublicKey();
        return key;
    }
    
    /**
     * 获得Certificate
     *
     * @param certificatePath
     * @return
     * @throws Exception
     */
    private static Certificate getCertificate(String certificatePath)
            throws Exception {
        CertificateFactory certificateFactory = CertificateFactory
                .getInstance(X509);
        FileInputStream in = new FileInputStream(certificatePath);
        
        Certificate certificate = certificateFactory.generateCertificate(in);
        in.close();
        
        return certificate;
    }
    
    /**
     * 公钥解密
     *
     * @param data
     * @param certificatePath
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
            throws Exception {
        // 取得公钥
        PublicKey publicKey = getPublicKey(certificatePath);
        
        // 对数据加密
        Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        
        return cipher.doFinal(data);
        
    }
    
    /**
     * 验证Certificate
     *
     * @param certificatePath
     * @return
     */
    public static boolean verifyCertificate(String certificatePath) {
        return verifyCertificate(new Date(), certificatePath);
    }
    
    /**
     * 验证Certificate是否过期或无效
     *
     * @param date
     * @param certificatePath
     * @return
     */
    public static boolean verifyCertificate(Date date, String certificatePath) {
        boolean status = true;
        try {
            // 取得证书
            Certificate certificate = getCertificate(certificatePath);
            // 验证证书是否过期或无效
            status = verifyCertificate(date, certificate);
        } catch (Exception e) {
            status = false;
        }
        return status;
    }
    
    /**
     * 验证证书是否过期或无效
     *
     * @param date
     * @param certificate
     * @return
     */
    private static boolean verifyCertificate(Date date, Certificate certificate) {
        boolean status = true;
        try {
            X509Certificate x509Certificate = (X509Certificate) certificate;
            x509Certificate.checkValidity(date);
        } catch (Exception e) {
            status = false;
        }
        return status;
    }
    
    /**
     * 验证Certificate
     *
     * @param keyStorePath
     * @param alias
     * @param password
     * @return
     */
    public static boolean verifyCertificate(String keyStorePath, String alias,
                                            String password) {
        return verifyCertificate(new Date(), keyStorePath, alias, password);
    }
    
    /**
     * 验证Certificate
     *
     * @param keyStorePath
     * @param alias
     * @param password
     * @return
     */
    public static boolean verifyCertificate(Date date, String keyStorePath,
                                            String alias, String password) {
        boolean status = true;
        try {
            Certificate certificate = getCertificate(keyStorePath, alias,
                    password);
            status = verifyCertificate(date, certificate);
        } catch (Exception e) {
            status = false;
        }
        return status;
    }
    
    /**
     * 获得Certificate
     *
     * @param keyStorePath
     * @param alias
     * @param password
     * @return
     * @throws Exception
     */
    private static Certificate getCertificate(String keyStorePath,
                                              String alias, String password) throws Exception {
        KeyStore ks = getKeyStore(keyStorePath, password);
        Certificate certificate = ks.getCertificate(alias);
        
        return certificate;
    }
    
    public static SSLSocketFactory getSSLSocketFactory(String caCertFile)
            throws Exception {
        FileInputStream inputStream = new FileInputStream(caCertFile);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        Certificate certificate = cf.generateCertificate(inputStream);
        String alias = ((X509Certificate) certificate).getSubjectDN().toString();
        
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        keyStore.setCertificateEntry(alias, certificate);
        
        SSLContext sslContext = SSLContext.getInstance("SSL", keyStore.getProvider());
        return sslContext.getSocketFactory();
    }
    
    /**
     * 获得SSLSocketFactory
     *
     * @param password     密码
     * @param keyStorePath 密钥库路径
     * @return SSLSocketFactory instance
     * @throws Exception
     */
    public static SSLSocketFactory getSSLSocketFactory(String password, String keyStorePath)
            throws Exception {
        SSLContext context;
        KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream fileIn = new FileInputStream(keyStorePath);
        ts.load(fileIn, password.toCharArray());
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(ts);
        TrustManager[] tm = tmf.getTrustManagers();
        context = SSLContext.getInstance("SSL");
        context.init(null, tm, null);
        
        return context.getSocketFactory();
    }
    
    /**
     * SSL Socket 对象创建工厂
     *
     * @param caCert    CA证书
     * @param clientCrt 客户证书
     * @param ClientKey 客户私有密钥
     * @param password  证书密码
     * @return
     * @throws Exception
     */
    public static SSLSocketFactory getSSLSocketFactory(String caCert, String clientCrt,
                                                       String ClientKey, String password)
            throws Exception {
        InputStream caIn = new FileInputStream(caCert);
        InputStream crtIn = new FileInputStream(clientCrt);
        
        // 获得CA证书
        CertificateFactory cAf = CertificateFactory.getInstance(X509);
        X509Certificate ca = (X509Certificate) cAf.generateCertificate(caIn);
        
        // 获得客户端证书
        CertificateFactory cf = CertificateFactory.getInstance(X509);
        X509Certificate x509Certificate = (X509Certificate) cf.generateCertificate(crtIn);
        crtIn.close();
        
        //创建空的keystore，添加CA证书、客户证书和客户私钥
        KeyStore caKs = KeyStore.getInstance(JKS_KEYSTORE);
        caKs.load(null, null);
        caKs.setCertificateEntry("ca-certificate", ca);
        caKs.setCertificateEntry("certificate", x509Certificate);
        caKs.setKeyEntry("private-key", getPrivateKey(ClientKey), password.toCharArray(),
                new Certificate[]{x509Certificate});
        
        //创建TrustManager和KeyManager，并用keystore初始化
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(PKIX);
        tmf.init(caKs);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(PKIX);
        kmf.init(caKs, password.toCharArray());
        
        // finally, create SSL socket factory
        SSLContext context = SSLContext.getInstance("TLSv1.2");
        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
        
        return context.getSocketFactory();
    }
    
    /**
     * 从私钥文件中获得私钥对象，步骤：
     * 1、Base64解码
     * 2、用PKCS8编码
     * 3、获得RSA加密过的RSA密钥
     *
     * @param path
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKey(String path) throws Exception {
        Base64 base64 = new Base64();
        byte[] buffer = Base64.decode(getPem(path));
        
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    }
    
    /**
     * 解析私钥文件内容为字符串
     *
     * @param path
     * @return
     * @throws Exception
     */
    private static String getPem(String path) throws Exception {
        InputStream fIn;
        File resFile = new File(path);
        fIn = new FileInputStream(resFile);
        BufferedReader br = new BufferedReader(new InputStreamReader(fIn));
        String readLine;
        StringBuilder sb = new StringBuilder();
        while ((readLine = br.readLine()) != null) {
            if (readLine.charAt(0) == '-') {
                continue;
            } else {
                sb.append(readLine);
                sb.append('\r');
            }
        }
        fIn.close();
        return sb.toString();
    }
    
}

